//============================================================================
// Name        : Rpb.cpp
// Author      : Agrond Pham
// Version     : 0.2.0
// Copyright   : Your copyright notice
// Description : KHNCamera in C++, Ansi-style



//changlog:
// DateTime       Version       Developer           Description

//16/10/2013        0.2.0       AGROND              Allow set size of screen by commanline
//16/10/2013        0.2.0       AGROND              Allow set size of screen by commanline

//17/10/2013        0.2.1       AGROND              Allow reconnect server if server off 


//============================================================================
#include <pthread.h>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <unistd.h>

#include <signal.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <iostream>
using namespace std;
using namespace cv;

VideoCapture    capture;
Mat             img0, img1;
int             is_data_ready = 1;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
Mat img2;
int             clientSock;
int       	server_port=5000;
const char* server_ip="127.0.0.1";
int widthPreview=640;
int heightPreview=480;

//for testing
#define TESTING 1 //change to 0  if does not test

//testing model
#define SHOW_FPS 1
#define SHOW_VIDEO 0


time_t start,end;
int counter=0;


struct  sockaddr_in serverAddr;
socklen_t  serverAddrLen = sizeof(serverAddr);


void closeSocket(){
    
    /*if (!img0.empty()){
        (img0.release());
    }*/
    
    if (!img1.empty()){
        (img1.release());
    }
    
    if (!img2.empty()){
        (img2.release());
    }
}
string CharToStr(const char* inputChar){
    string output(inputChar);
    return output;
}
void quit(string msg, int retval)
{
    if (retval == 0) {
        cout << (msg == "NULL" ? "" : msg) << "\n" <<endl;
    } else {
        cerr << (msg == "NULL" ? "" : msg) << "\n" <<endl;
    }

    if (clientSock){
        close(clientSock);
    }

    if (capture.isOpened()){
        capture.release();
    }

    if (!img0.empty()){
        (img0.release());
    }

    if (!img1.empty()){
        (img1.release());
    }

    if (!img2.empty()){
        (img2.release());
    }

    pthread_mutex_destroy(&mutex);
    exit(retval);
}
void connectServer(sockaddr_in serverAddr,socklen_t  serverAddrLen){
    while (1) {
        cout << "Waiting connection" << "\n" <<endl;
        if ((clientSock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
            quit("\n--> socket() failed.", 1);
        }
#ifdef __APPLE__
        int set=1;
        if(setsockopt(clientSock, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int))<0){
            quit("\n--> setsock error.", 1);
        }
#endif
        serverAddr.sin_family = PF_INET;
        serverAddr.sin_addr.s_addr = inet_addr(server_ip);
        serverAddr.sin_port = htons(server_port);
        if(connect(clientSock, (sockaddr*)&serverAddr, serverAddrLen) >= 0){
            cout << "Connected" << "\n" <<endl;
            break;
        }
        usleep(3000);
    }
    //return NULL;
}
void* sendCameraData(void* arg){
    int  imgSize = (int)(img1.total()*img1.elemSize());
    int  bytes=0;
    img2 = (img1.reshape(0,1)); // to make it continuous
    /* start sending images */
    while(1)
    {
        /* send the grayscaled frame, thread safe */
        if (is_data_ready) {
            pthread_mutex_lock(&mutex);
#ifdef __linux
            
            bytes = (int)send(clientSock, img2.data, imgSize, MSG_NOSIGNAL);
#else
            bytes = (int)send(clientSock, img2.data, imgSize, 0);
#endif
            if (bytes < 0){
                cerr << "\n--> bytes = " << bytes << endl;
                cerr << "\n--> send() failed error no = " << errno << endl;
                //try to close and reset connect of socket
                //closeSocket();
                is_data_ready = 1;
                pthread_mutex_unlock(&mutex);
                memset(&serverAddr, 0x0, serverAddrLen);
                //try to connect server
                //connectServer(serverAddr,serverAddrLen);
                return NULL;
            }else{
                is_data_ready = 0;
                pthread_mutex_unlock(&mutex);
                memset(&serverAddr, 0x0, serverAddrLen);
            }
            
        }
        /* if something went wrong, restart the connection */
        if (bytes != imgSize) {
            cerr << "\n-->  Connection closed (bytes != imgSize)" << endl;
            //Try to close and clear data of socket
            //closeSocket();
            //try to connect server
            //connectServer(serverAddr,serverAddrLen);
            return NULL;
        }
        /* have we terminated yet? */
        //pthread_testcancel();
        
        /* no, take a rest for a while */
        usleep(1000);   //1000 Micro Sec
    }
    return NULL;
    
}
void* openStreamVideo(void* arg){
    //struct  sockaddr_in serverAddr;
    //socklen_t  serverAddrLen = sizeof(serverAddr);

    /* make this thread cancellable using pthread_cancel() */
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
    //try to connect server
    //connectServer(serverAddr,serverAddrLen);
    
    
    
    img2 = (img1.reshape(0,1)); // to make it continuous
    sendCameraData(NULL);
        //if (pthread_create(&thread_t, NULL, &openStreamVideo, NULL)) {
    //    quit("\n--> pthread_create failed.", 1);
    //}
    //cerr << "\n--> wait connect" << endl;
    openStreamVideo(NULL);
    return NULL;
}
string retrieveCPUSeria(){
#if TESTING==1
    FILE *f= fopen("cpuinfo", "r");
#else
    FILE *f= fopen("proc/cpuinfo", "r");
#endif
    if(!f){
        return NULL;
    }
    char line[256];
    while (fgets(line, 256, f)) {
        if(strncmp(line, "Serial", 6)==0){
            char serial_string[16+1];
            string serial(strcpy(serial_string, strchr(line, ':')+2));
            return serial;
        }
    }
    fclose(f);
    return NULL;
}
void mainRpbWithAuth(){
    //Get CPU serial
	time(&start);
    int key=0;
    pthread_t   thread_c;
    
    //Capture from camera
    capture.open(-1);
    if (!capture.isOpened()) {
        cout << "Cannot open camera, system  is trying" << "\n" <<endl;
        //quit("\n--> cvCapture failed retry", 1);
        //while (!capture.isOpened()) {
        //    capture.open(0);
        //    usleep(3000);
        //}
        usleep(30000);
    }
    //capture.open(0);
    //Set Video Capture size
    capture.set(CV_CAP_PROP_FRAME_WIDTH,widthPreview);
    capture.set(CV_CAP_PROP_FRAME_HEIGHT,heightPreview);
    capture >> img0;
    img1 = Mat::zeros(img0.rows, img0.cols ,CV_8UC1);


    // run the streaming client as a separate thread
    if (pthread_create(&thread_c, NULL, &openStreamVideo, NULL)) {
        quit("\n--> pthread_create failed.", 1);
    }

    //string windowName=createUI();
    //showIn(key,"stream_client",img0,img1);
    flip(img0, img0, 1);
    cvtColor(img0, img1, CV_BGR2GRAY);


    while(key != 'q') {
        /* get a frame from camera */
        capture >> img0;
        if (img0.empty()) break;
        pthread_mutex_lock(&mutex);

        flip(img0, img0, 1);
        cvtColor(img0, img1, CV_BGR2GRAY);

        is_data_ready = 1;

        pthread_mutex_unlock(&mutex);

        
#if TESTING==1 && SHOW_VIDEO==1
        //Create UI
        const string& windowName="stream_client";
        namedWindow(windowName, CV_WINDOW_AUTOSIZE);
        /*also display the video here on client */
        imshow(windowName, img0);
#endif
#if TESTING==1 && SHOW_FPS==1
        time(&end);
        ++counter;
        cout << "fps:"<<counter/difftime(end,start)<<endl;
#endif
        key = waitKey(10);

    }
    /* user has pressed 'q', terminate the streaming client */
    if (pthread_cancel(thread_c)) {
        quit("\n--> pthread_cancel failed.", 1);
    }

    /* free memory */
    destroyWindow("stream_client");
    quit("\n--> NULL", 0);
}
int checkAuthentication(string cpu_serial){
    bool authenticated=false;
    for (int i=0; i<3; i++) {
        if ((clientSock = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
            quit("\n--> socket() failed.", 1);
        }
#ifdef __APPLE__
        int set=1;
        if(setsockopt(clientSock, SOL_SOCKET, SO_NOSIGPIPE, (void*)&set, sizeof(int))<0){
            quit("\n--> setsock error.", 1);
        }
#endif
        serverAddr.sin_family = PF_INET;
        serverAddr.sin_addr.s_addr = inet_addr(server_ip);
        serverAddr.sin_port = htons(server_port);
        if(connect(clientSock, (sockaddr*)&serverAddr, serverAddrLen) >= 0){
            cout << "Connected" << "\n" <<endl;
            //send cpuid
            int  authenticationBytes=0;
#ifdef __linux
            
            authenticationBytes = (int)send(clientSock, cpu_serial.c_str(), strlen(cpu_serial.c_str()), MSG_NOSIGNAL);
#else
            authenticationBytes = (int)send(clientSock, cpu_serial.c_str(), strlen(cpu_serial.c_str()), 0);
#endif
            if (authenticationBytes < 0){
                cerr << "\n--> bytes = " << authenticationBytes << endl;
                cerr << "\n--> send() failed error no = " << errno << endl;
                memset(&serverAddr, 0x0, serverAddrLen);
                //return NULL;
            }else{
                char sockAuthData[256];
                if ((authenticationBytes = (int)recv(clientSock, sockAuthData, 2000, 0)) > 0) {
                    cout << "REV DATA"<< sockAuthData <<endl;
                    
                    if(CharToStr(sockAuthData)=="ok"){
                        authenticated=true;
                        
                        //call funtion send data
                        return 1;
                    }
                }
                memset(&serverAddr, 0x0, serverAddrLen);
            }
        }
        
    }
    return 0;
}
int main(int argc, char** argv)
{
    
    string cpu_serial="KHNA "+retrieveCPUSeria();
    cout << "CPU serial" << cpu_serial <<endl;
    server_ip = argc > 1? argv[1]:"192.168.1.102";
	server_port = atoi(argc>2?argv[2]:"5000");
    widthPreview = atoi(argc>3? argv[3]:"320");
	heightPreview = atoi(argc>4?argv[4]:"240");
    //check connect is authentication
    
    if(checkAuthentication(cpu_serial)){
        mainRpbWithAuth();
    }
    
    return 0;
}
